﻿#region 信息

// Copyright©2014 Rex Rao all rights reserved.
// File name : Mojito\Sohobloo.SoMcCoffee\EfRepositoryBase.cs
// Author : Sohobloo
// Create time : 2014/07/09 11:12
// Update time : 2014/08/19 11:37

#endregion

#region 引用

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Linq.Expressions;
using Sohobloo.SoCommon;
using Sohobloo.SoCommon.CommonExtension;
using Sohobloo.SoMcCoffee.Entity;

#endregion

namespace Sohobloo.SoMcCoffee.Data
{
    /// <summary>
    ///     EntityFramework仓储操作基类
    /// </summary>
    /// <typeparam name="TEntity">动态实体类型</typeparam>
    public abstract class EfRepositoryBase<TEntity> : IRepository<TEntity> where TEntity : EntityBase
    {
        private const int SortIncreament = 10;

        public bool AutoSave = false;

        #region 属性

        /// <summary>
        ///     获取或设置 EntityFramework的数据仓储上下文
        /// </summary>
        protected IUnitOfWorkContext EfContext
        {
            get
            {
                //if (UnitOfWork is IUnitOfWorkContext)
                //{
                return UnitOfWork as IUnitOfWorkContext;
                //}
                //throw new DataAccessException(string.Format("数据仓储上下文对象类型不正确，应为IUnitOfWorkContext，实际为 {0}", UnitOfWork.GetType().Name));
            }
        }

        /// <summary>
        ///     获取 仓储上下文的实例
        /// </summary>
        [Import]
        public IUnitOfWork UnitOfWork { get; set; }

        /// <summary>
        ///     获取 当前实体的查询数据集
        /// </summary>
        public virtual IQueryable<TEntity> Entities
        {
            get { return EfContext.Set<TEntity>(); }
        }

        #endregion 属性

        #region 方法

        public virtual TEntity GetByKey(Guid key)
        {
            return EfContext.Set<TEntity>().Find(key);
        }

        /// <summary>
        ///     插入实体记录
        /// </summary>
        /// <param name="entity"> 实体对象 </param>
        /// <returns> 操作影响的行数 </returns>
        public virtual int Insert(TEntity entity)
        {
            ArgumentHelper.Instance.CheckNull(entity);
            EfContext.RegisterNew(entity);
            return AutoSave ? EfContext.Commit() : 0;
        }

        /// <summary>
        ///     批量插入实体记录集合
        /// </summary>
        /// <param name="entities"> 实体记录集合 </param>
        /// <returns> 操作影响的行数 </returns>
        public virtual int Insert(IEnumerable<TEntity> entities)
        {
            ArgumentHelper.Instance.CheckNull(entities);
            EfContext.RegisterNew(entities);
            return AutoSave ? EfContext.Commit() : 0;
        }

        /// <summary>
        ///     更新实体记录
        /// </summary>
        /// <param name="entity"> 实体对象 </param>
        /// <param name="logModification"> 是否记录变更 </param>
        /// <returns> 操作影响的行数 </returns>
        public virtual int Update(TEntity entity, bool logModification = true)
        {
            ArgumentHelper.Instance.CheckNull(entity);
            EfContext.RegisterModified(entity, logModification);
            return AutoSave ? EfContext.Commit() : 0;
        }

        /// <summary>
        ///     批量更新实体记录
        /// </summary>
        /// <param name="entities"> 实体记录集合 </param>
        /// <param name="logModification"> 是否记录变更 </param>
        /// <returns> 操作影响的行数 </returns>
        public virtual int Update(IEnumerable<TEntity> entities, bool logModification = true)
        {
            ArgumentHelper.Instance.CheckNull(entities);
            EfContext.RegisterModified(entities, logModification);
            return AutoSave ? EfContext.Commit() : 0;
        }

        /// <summary>
        ///     删除指定编号的记录
        /// </summary>
        /// <param name="key"> 实体记录编号 </param>
        /// <returns> 操作影响的行数 </returns>
        public virtual int Delete(Guid key)
        {
            var entity = EfContext.Set<TEntity>().Find(key);
            return entity.IsNotNull() ? Delete(entity) : 0;
        }

        /// <summary>
        ///     删除实体记录
        /// </summary>
        /// <param name="entity"> 实体对象 </param>
        /// <returns> 操作影响的行数 </returns>
        public virtual int Delete(TEntity entity)
        {
            ArgumentHelper.Instance.CheckNull(entity);
            EfContext.RegisterDeleted(entity);
            return AutoSave ? EfContext.Commit() : 0;
        }

        /// <summary>
        ///     删除实体记录集合
        /// </summary>
        /// <param name="entities"> 实体记录集合 </param>
        /// <returns> 操作影响的行数 </returns>
        public virtual int Delete(IEnumerable<TEntity> entities)
        {
            ArgumentHelper.Instance.CheckNull(entities);
            EfContext.RegisterDeleted(entities);
            return AutoSave ? EfContext.Commit() : 0;
        }

        /// <summary>
        ///     删除所有符合特定表达式的数据
        /// </summary>
        /// <param name="predicate"> 查询条件谓语表达式 </param>
        /// <returns> 操作影响的行数 </returns>
        public virtual int Delete(Expression<Func<TEntity, bool>> predicate)
        {
            ArgumentHelper.Instance.CheckNull(predicate);
            var entities = EfContext.Set<TEntity>().Where(predicate).ToList();
            return entities.Count > 0 ? Delete(entities) : 0;
        }

        /// <summary>
        ///     修改排序
        /// </summary>
        /// <param name="entityId">要修改的实体ID</param>
        /// <param name="prevEntityId">上一个参照实体ID</param>
        /// <returns>操作影响行数</returns>
        public int ChangeSort(Guid entityId, Guid? prevEntityId)
        {
            var entity = GetByKey(entityId);
            ArgumentHelper.Instance
                          .CheckNull(entity)
                          .Check(entity.EnableSort, "EnableSort");

            var changedCount = 0;
            if(prevEntityId.HasValue)
            {
                var prevEntity = GetByKey(prevEntityId.Value);
                ArgumentHelper.Instance.CheckNull(prevEntity);
                var prevSortIndex = prevEntity.SortIndex;
                var newSortIndex = prevSortIndex + SortIncreament;
                changedCount += ChangeAssociatedSort(prevSortIndex, newSortIndex);
                entity.SortIndex = newSortIndex;
            }
            else
            {
                var firstEntity = Entities.OrderBy(e => e.SortIndex).FirstOrDefault();
                if(firstEntity.IsNull())
                {
                    entity.SortIndex = DateTime.UtcNow.Ticks;
                }
                else
                {
                    entity.SortIndex = firstEntity.SortIndex - SortIncreament;
                }
            }
            changedCount += Update(entity);
            return changedCount;
        }

        /// <summary>
        ///     修改排序造成的连带改变
        /// </summary>
        /// <param name="startSortIndex"></param>
        /// <param name="endSortIndex"></param>
        /// <returns></returns>
        private int ChangeAssociatedSort(long startSortIndex, long endSortIndex)
        {
            var changedCount = 0;
            var associatedEntities =
                Entities.Where(entity => entity.SortIndex > startSortIndex && entity.SortIndex <= endSortIndex)
                        .OrderByDescending(entity => entity.SortIndex)
                        .ToList();
            var entityCount = associatedEntities.Count;
            for(var i = 0; i < entityCount; i++)
            {
                var entity = associatedEntities[i];
                var oldSortIndex = entity.SortIndex;
                var newSortIndex = endSortIndex + SortIncreament * (i + 1);
                if(i + 1 == entityCount)
                {
                    changedCount += ChangeAssociatedSort(oldSortIndex, newSortIndex);
                }
                entity.SortIndex = newSortIndex;
                changedCount += Update(entity, false);
            }
            return changedCount;
        }

        #endregion 方法
    }
}